from urllib import parse
import typing

from django.utils.translation import gettext_lazy, gettext as _
from django.http import HttpResponseRedirect
from rest_framework.serializers import Serializer
from rest_framework.decorators import action
from rest_framework.response import Response
from drf_yasg.utils import swagger_auto_schema, no_body
from drf_yasg import openapi

from core.aai.jwt_backends import JWTInvalidError
from apps.api.viewsets import NormalGenericViewSet
from apps.app_aai_proxy.managers import aai_proxy_configs, UserInfoManager, RefreshToken, UntypedToken
from apps.app_aai_proxy.oidc_client import AAIProxyHelper, OIDCClient
from apps.app_aai_proxy.models import AAIUserInfo


class AAILoginViewSet(NormalGenericViewSet):
    permission_classes = []
    pagination_class = None
    lookup_field = 'id'

    @swagger_auto_schema(
        tags=['aai_proxy'],
        operation_summary=gettext_lazy('获取AAI登录地址'),
        deprecated=True,
        request_body=no_body,
        manual_parameters=[
            openapi.Parameter(
                name='clientUrl',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=True,
                description=gettext_lazy('填写自己应用前端回调地址')
            ),
        ],
        responses={
            200: ''''''
        }
    )
    @action(methods=['post'], detail=False, url_path='askUrl', url_name='ask-url')
    def ask_url(self, request, *args, **kwargs):
        """
        获取AAI登录地址

            http code 200:
            {
                "success":true,
                "code":200,
                "message":"请求成功",
                "data":"https://xxx"
            }
        """
        client_url = request.query_params.get('clientUrl', None)
        if not client_url:
            return self.build_error_response(message=_('clientUrl不能为空，请填写客户端回调地址'))

        if not AAIProxyHelper.allow_client(client_url):
            return self.build_error_response(message=_('clientUrl无权访问，请联系系统管理员'))

        query = parse.urlencode(query={
            'identifier': aai_proxy_configs.get(aai_proxy_configs.ConfigName.AAI_ISSUER.value),
            'clientUrl': client_url,
        })
        auth_url = AAIProxyHelper.get_aai_proxy_url() + '?' + query
        return self.build_success_response(data=auth_url)

    @swagger_auto_schema(
        tags=['aai_proxy'],
        deprecated=True,
        operation_summary=gettext_lazy('获取aai登陆地址，并直接跳转到aai'),
        manual_parameters=[
            openapi.Parameter(
                name='clientUrl',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=True,
                description=gettext_lazy('填写自己应用前端回调地址')
            ),
        ],
        responses={
            200: ''''''
        }
    )
    @action(methods=['get'], detail=False, url_path='askUrlRedirect', url_name='ask-url-redirect')
    def ask_url_redirect(self, request, *args, **kwargs):
        """
        获取aai登陆地址，并直接跳转到aai

            获取aai登陆地址，并直接跳转到AAI,clientUrl填写自己应用前端回调地址

            http code 302 redirect
        """
        client_url = request.query_params.get('clientUrl', None)
        if not client_url:
            return self.build_error_response(message=_('clientUrl不能为空，请填写客户端回调地址'))

        if not AAIProxyHelper.allow_client(client_url):
            return self.build_error_response(message=_('clientUrl无权访问，请联系系统管理员'))

        auth_url = AAIProxyHelper.get_aai_proxy_url() + f'?clientUrl={client_url}'
        return HttpResponseRedirect(redirect_to=auth_url)

    @swagger_auto_schema(
        tags=['aai_proxy'],
        deprecated=True,
        operation_summary=gettext_lazy('登陆认证后跳转到前端应用的回调地址并携带着code，前端应用通过code来换取jwt'),
        request_body=no_body,
        manual_parameters=[
            openapi.Parameter(
                name='code',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=True,
                description=gettext_lazy('aai登录认证后回调返回的参数 code')
            ),
        ],
        responses={
            200: ''''''
        }
    )
    @action(methods=['post'], detail=False, url_path='dealCode', url_name='deal-code')
    def deal_code(self, request, *args, **kwargs):
        """
        登陆认证后跳转到前端应用的回调地址并携带着code，前端应用通过code来换取jwt

            http code 200:
            {
                "success":true,
                "code":200,
                "message":"请求成功",
                "data": {
                    "accessToken": "xx",
                    "refreshToken": "xxx"
                }
            }
        """
        code = request.query_params.get('code', None)
        if not code:
            return self.build_error_response(message=_('必须提交code'))

        oidc_client = OIDCClient.build_client()
        try:
            token = oidc_client.code2token(code=code)
        except Exception as e:
            return self.build_error_response(message=_('从AAI获取toekn信息失败，提供的code无效或者已过期。') + str(e))

        try:
            user_info = oidc_client.get_user_info(token=token)
        except Exception as e:
            return self.build_error_response(message=_('从AAI获取用户信息失败。') + str(e))

        source_refresh_token = token.get('refresh_token', '')
        try:
            user = UserInfoManager.record_user(
                source_id=user_info['sub'], # AAI user id
                auth_type=AAIUserInfo.UserType.AAI.value,
                true_name=user_info['name'],
                email=user_info['email'],
                org_name=user_info['orgName'],
            )
        except Exception as e:
            return self.build_error_response(message=_('创建或更新从AAI获取用户信息错误。') + str(e))

        refresh_token = RefreshToken.for_user(user, source_refresh_token=source_refresh_token)
        access_token = refresh_token.access_token(user)

        return self.build_success_response(
            data={
                'accessToken': str(access_token),
                'refreshToken': str(refresh_token)
            }
        )

    @swagger_auto_schema(
        tags=['aai_proxy'],
        deprecated=True,
        operation_summary=gettext_lazy('token合法性校验'),
        request_body=no_body,
        manual_parameters=[
            openapi.Parameter(
                name='jwtToken',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=True,
                description=gettext_lazy('需要检测合法性的token字符串')
            ),
        ],
        responses={
            200: ''''''
        }
    )
    @action(methods=['post'], detail=False, url_path='checkToken', url_name='check-token')
    def check_token(self, request, *args, **kwargs):
        """
        token合法性校验

            http code 200:
            {
                "success":true,
                "code":200,     # 200 or 500
                "message":"xx",
                "data": true    # true or false
            }
        """
        jwt_token = request.query_params.get('jwtToken', None)
        if not jwt_token:
            return self.build_error_response(message=_('参数jwtToken必须提交'), data=False)

        try:
            UntypedToken(jwt_token, verify=True)
        except JWTInvalidError as exc:
            return self.build_error_response(message=str(exc), data=False)

        return self.build_success_response(message=_('jwtToken合法性校验'), data=True)

    @swagger_auto_schema(
        tags=['aai_proxy'],
        deprecated=True,
        operation_summary=gettext_lazy('续期token'),
        request_body=no_body,
        manual_parameters=[
            openapi.Parameter(
                name='refreshToken',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=True,
                description=gettext_lazy('续期需要的 refreshToken')
            ),
        ],
        responses={
            200: ''''''
        }
    )
    @action(methods=['post'], detail=False, url_path='refreshToken', url_name='refresh-token')
    def refresh_token(self, request, *args, **kwargs):
        """
        续期token

            在有效期内访问本接口，续期token.如果返回值中data为false，说明续签失败，失败原因从message取。

            http code 200:
            {
                "success":true,
                "code":200,     # 200 or 500
                "message":"xx",
                "data": {
                    "accessToken": "xx",
                    "refreshToken": "xxx"
                }
            }
        """
        jwt_token = request.query_params.get('refreshToken', None)
        if not jwt_token:
            return self.build_error_response(message=_('参数refreshToken必须提交'), data=False)

        try:
            refresh_token = RefreshToken(jwt_token, verify=True)
        except JWTInvalidError as exc:
            return self.build_error_response(message=str(exc), data=False)

        user_id = refresh_token.user_info_id()
        user_info = UserInfoManager.get_user_info(user_id=user_id)
        if user_info is None:
            return self.build_error_response(message=_('用户信息不存在'), data=False)

        access_token = refresh_token.access_token(user_info)
        return self.build_success_response(
            message='refresh token success',
            data={
                'accessToken': str(access_token),
                'refreshToken': jwt_token
            })

    @swagger_auto_schema(
        tags=['aai_proxy'],
        deprecated=True,
        operation_summary=gettext_lazy('AAI退出'),
        manual_parameters=[
            openapi.Parameter(
                name='loginOutUrl',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=True,
                description=gettext_lazy('业务系统退出登陆后的回调地址')
            ),
        ],
        responses={
            200: ''''''
        }
    )
    @action(methods=['get'], detail=False, url_path='loginOut', url_name='login-out')
    def login_out(self, request, *args, **kwargs):
        """
        AAI退出

            由aai退出原理控制，退出逻辑需要重定向，最终会在aai统一退出登陆。
            http code 302 redirect
        """
        login_out_url = request.query_params.get('loginOutUrl', '')
        aai_logout_url = AAIProxyHelper.build_aai_logout_url(login_out_url)
        return HttpResponseRedirect(redirect_to=aai_logout_url)

    @swagger_auto_schema(
        tags=['aai_proxy'],
        deprecated=True,
        operation_summary=gettext_lazy('获取AAI退出地址'),
        request_body=no_body,
        manual_parameters=[
            openapi.Parameter(
                name='loginOutUrl',
                in_=openapi.IN_QUERY,
                type=openapi.TYPE_STRING,
                required=True,
                description=gettext_lazy('业务系统退出登陆后的回调地址')
            ),
        ],
        responses={
            200: ''''''
        }
    )
    @action(methods=['post'], detail=False, url_path='loginOutSelf', url_name='login-out-self')
    def login_out_self(self, request, *args, **kwargs):
        """
        获取AAI退出地址

            http code 200:
            {
                "success":true,
                "code":200,     # 200 or 500
                "message":"xx",
                "data": "https://xxx"
            }
        """
        login_out_url = request.query_params.get('loginOutUrl', '')
        aai_logout_url = AAIProxyHelper.build_aai_logout_url(login_out_url)
        return self.build_success_response(data=aai_logout_url)

    @swagger_auto_schema(
        tags=['aai_proxy'],
        deprecated=True,
        operation_summary=gettext_lazy('代理服务的从AAI登出的回调地址'),
        responses={
            302: ''''''
        }
    )
    @action(methods=['get'], detail=False, url_path='outRedirect', url_name='logout-redirect')
    def logout_redirect(self, request, *args, **kwargs):
        """
        代理服务的从AAI登出的回调地址
        """
        state = request.query_params.get('state', '')
        return HttpResponseRedirect(redirect_to=state)

    @staticmethod
    def build_response(success: bool, code: int, message: str, data: typing.Union[str, bool, dict, None]) -> Response:
        return Response({
            'success': success,
            'code': code,
            'message': message if message else '',
            'data': data
        })

    def build_error_response(self, message: str, data: typing.Union[str, bool, dict, None] = None) -> Response:
        return self.build_response(
            success=False, code=500, message=message, data=data
        )

    def build_success_response(self, data: typing.Union[str, bool, dict, None], message: str = None) -> Response:
        message = message if message else 'success'
        return self.build_response(
            success=True, code=200, message=message, data=data
        )

    def get_serializer_class(self):
        return Serializer
